package com.ea.admin.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ea.admin.config.AuthorizationServerConfig;
import com.ea.admin.mapper.AdminAccountMapper;
import com.ea.admin.pojo.dto.AdminAccountDto;
import com.ea.admin.pojo.po.AdminAccount;
import com.ea.admin.pojo.vo.account.AdminInfoVo;
import com.ea.admin.pojo.vo.auth.AuthVo;
import com.ea.admin.service.AuthService;
import com.ea.admin.service.CompanyPermissionService;
import com.ea.admin.service.DeptPermissionService;
import com.ea.admin.service.FunctionPermissionService;
import com.ea.common.auth.NowUser;
import com.ea.common.result.BizException;
import com.ea.common.result.ErrorCode;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
public class AuthServiceImpl implements AuthService {
    @Resource
    RedisTemplate<String, Object> redisTemplate;
    @Resource
    FunctionPermissionService functionPermissionService;
    @Resource
    CompanyPermissionService companyPermissionService;
    @Resource
    DeptPermissionService deptPermissionService;
    @Resource
    AdminAccountMapper adminAccountMapper;
    @Resource
    AuthorizationServerConfig authorizationServerConfig;
    @Resource
    TokenEndpoint tokenEndpoint;
    @Resource
    BCryptPasswordEncoder bCryptPasswordEncoder;


    @Override
    public AuthVo login(String username, String password) {
        // 创建客户端信息,客户端信息可以写死进行处理，因为Oauth2密码模式，客户端双信息必须存在，所以伪装一个
        // 如果不想这么用，需要重写比较多的代码
        // 这里设定，调用这个接口的都是资源服务
        User clientUser = new User("client", "EmoAnaly", new ArrayList<>());
        // 生成已经认证的client
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(clientUser, null,
                new ArrayList<>());
        // 封装成一个UserPassword方式的参数体
        Map<String, String> map = new HashMap<>();
        map.put("username", username);
        map.put("password", password);
        // 授权模式为：密码模式
        map.put("grant_type", "password");
        // 24小时 -> s
        map.put("expires_in", "86400");

        // 调用自带的获取token方法。
        OAuth2AccessToken resultToken;
        try {
            resultToken = tokenEndpoint.postAccessToken(token, map).getBody();
            AuthVo authVo = new AuthVo();
            authVo.setAccessToken(resultToken.getValue());
            authVo.setTokenType(resultToken.getTokenType());

            LambdaQueryWrapper<AdminAccount> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(AdminAccount::getUsername, username);
            AdminAccount adminAccount = adminAccountMapper.selectOne(wrapper);

            // 判断用户是否被禁用
            if (adminAccount.getIsBlock() == 1) {
                throw new DisabledException("账户已被禁用");
            }

            // 将用户信息存入Redis中
            // 将Key为username，Value为account_id，存入Redis中
            NowUser nowUser = new NowUser(adminAccount.getAccountId());
            // 存入 Username-> NowUser 键值对，24个小时过期
            redisTemplate.opsForValue().set(USERNAME_TO_NOWUSER_REDIS_KEY_PREFIX + adminAccount.getUsername(), nowUser, 24, TimeUnit.HOURS);

            return authVo;
        } catch (DisabledException e) {
            throw new BizException(ErrorCode.USER_LOGIN_ERROR_ACCOUNT_BLOCKED);
        } catch (Exception e) {
            throw new BizException(ErrorCode.USER_LOGIN_ERROR_ACCOUNT_PASSWORD_NOT_MATCH);
        }
    }

    @Override
    public Boolean checkPassword(String username, String password) {
        try {
            login(username, password);
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    @Override
    public void logout(Long accountId, String token) {
        // 判断 authorization 是否为空
        if (token.isEmpty()) {
            return;
        }
        // 判断 bearer token 是否为空
        if (token.contains("bearer ")) {
            token = token.replace("bearer ", "");
        }
        // 清除 redis token 信息
        TokenStore redisTokenStore = authorizationServerConfig.tokenStore();
        OAuth2AccessToken oAuth2AccessToken = redisTokenStore.readAccessToken(token);
        if (oAuth2AccessToken != null) {
            redisTokenStore.removeAccessToken(oAuth2AccessToken);
        }

        // 清除 redis nowUser 信息
        LambdaQueryWrapper<AdminAccount> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(AdminAccount::getAccountId, accountId);
        AdminAccount adminAccount = adminAccountMapper.selectOne(wrapper);
        if (adminAccount != null) {
            redisTemplate.opsForValue().getAndDelete(USERNAME_TO_NOWUSER_REDIS_KEY_PREFIX +
                    adminAccount.getUsername());
        }
    }

    @Override
    public void changePassword(Long accountId, String oldPassword, String password, String token) {
        // 1. 查出用户的 AdminAccount
        LambdaQueryWrapper<AdminAccount> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(AdminAccount::getAccountId, accountId);
        AdminAccount userAccount = adminAccountMapper.selectOne(wrapper);
        if (userAccount == null) {
            throw new BizException(ErrorCode.SYSTEM_RESOURCE_ERROR_USER_NOT_EXISTS);
        }
        // 2. 确认旧密码是否正确
        if (!checkPassword(userAccount.getUsername(), oldPassword)) {
            throw new BizException(ErrorCode.USER_LOGIN_ERROR_PASSWORD_ERROR);
        }

        // 3. 将密码用Bcrypt加密，存入数据库
        String encodePassword = bCryptPasswordEncoder.encode(password);
        userAccount.setPassword(encodePassword);
        adminAccountMapper.updateById(userAccount);

        // 4. 将用户设置为登出
        logout(accountId, token);
    }



    @Override
    public AdminInfoVo getAdminInfo(Long accountId) {
        AdminAccountDto adminAccountDto = adminAccountMapper.selectAdminAccountDtoByAccountId(accountId);
        AdminInfoVo result = parseAdminAccountDtoToVo(adminAccountDto);

        // 为管理员添加权限信息
        result.setFunctionPermissionVoList(
                functionPermissionService.getFunctionPermissionByAccountId(accountId)
        );
        result.setCompanyPermissionList(
                companyPermissionService.getCompanyPermission(accountId)
        );
        result.setDepartmentPermissionList(
                deptPermissionService.getDeptPermission(
                        null,
                        accountId)
        );
        return result;
    }


    private AdminInfoVo parseAdminAccountDtoToVo(AdminAccountDto adminAccount) {
        AdminInfoVo adminInfoVo = new AdminInfoVo();
        adminInfoVo.setAccountId(adminAccount.getAccountId().toString());
        adminInfoVo.setName(adminAccount.getName());
        adminInfoVo.setPhoneNumber(adminAccount.getPhoneNumber());
        adminInfoVo.setUsername(adminAccount.getUsername());
        adminInfoVo.setIsBlock(adminAccount.getIsBlock());
        adminInfoVo.setAdminRoleId(adminAccount.getAdminRoleId());
        adminInfoVo.setAdminRoleName(adminAccount.getAdminRoleName());
        return adminInfoVo;
    }

    @Override
    public NowUser getNowUserByUsername(String username) {
        // 先在Redis中查询
        NowUser nowUser = (NowUser) redisTemplate.opsForValue().get(AuthService.USERNAME_TO_NOWUSER_REDIS_KEY_PREFIX + username);

        // 如果Redis中查询不到，则在数据库中查询
        if (nowUser == null) {
            QueryWrapper<AdminAccount> queryWrapper = new QueryWrapper<>();
            queryWrapper.lambda().eq(AdminAccount::getUsername, username);
            AdminAccount userAccount = adminAccountMapper.selectOne(queryWrapper);

            // 如果查询不到此账户，说明用户已经注销了，但是抛出“登录用户不存在”的错误信息以保密
            if (userAccount == null) {
                throw new BizException(ErrorCode.USER_LOGIN_ERROR_USER_NOT_EXISTS);
            }
            nowUser = new NowUser(userAccount.getAccountId());
            // 存入username：NowUser键值对，12个小时过期
            redisTemplate.opsForValue().set(AuthService.USERNAME_TO_NOWUSER_REDIS_KEY_PREFIX + userAccount.getUsername(), nowUser, 24, TimeUnit.HOURS);
        }
        return nowUser;
    }


}
